Android NDK的使用实例——增量更新实战

image

1、概述

Android很多应用没有使用到NDK开发,但想要开发更高级的应用,NDK的学习是必然之路。NDK的好处不多说,这里也应该说是JNI的好处,其中之一就是可以方便使用到C/C++世界里面的优秀开源库,这里要实战的是增量更新,其中用到的是bsdiff开源代码,而bsdiff又依赖bzip2开源代码。

一开始自己做过一些硬件开发,也使用过一些so库,使用的话按照文档指示一般没什么问题,但实际上对NDK开发流程总有一些疑问。比如说

  • 如何创建so库,so库是怎么生成的?
  • 假如我生成了so库,如何给其他应用调用这个so库?
  • jni目录、cpp目录又是什么?为什么有些项目使用的不一样
  • Android.mk、Application.mk是什么?
  • NDK开发一定要使用到javah、ndk-build命令吗?
  • CMake、CMakeLists.txt又是什么?

对这些问题同样存在疑问的,可以参考网上的一些博客,写的非常详细
Android NDK 开发从 0 到 1
Android NDK开发扫盲及最新CMake的编译使用
AndroidStudio中使用JNI/NDK示例

产生这些问题的原因在于平时只看书、博客是会忽略掉很多细节,从而产生这样的疑问,所以NDK的学习之路,必然需要动手操作。

增量更新不同于热更新,增量更新可以应用于app市场,避免用户用过多的流量去升级app,只需要下载差分部分的patch补丁就可以,更快速、节省流量的实现了app的更新。

2、在mac上实现增量更新

我用的是mac系统,win应该也差不多,先下载文件

Binary diff/patch utility
bzip2

2.1、使用make命令生成bsdiff、bspatch可执行文件
lexdeMacBook-Pro:bsdiff-4.3 lex$ make

遇到问题

Mac下xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

解决方法

xcode-select --install

重新安装xcode-select就可以
执行make命令后还是出现以下问题

Makefile:13: *** missing separator.  Stop.

参考网上说对Makefile文件中 if/endif 做了缩进,没效果。这里是一个坑,我用AndroidStudio去编辑无效,后来使用vim来编辑就可以了。

image

这时候生成了bsdiff可执行文件,接着出现以下错误,无法生成bspatch可执行文件,虽然说合并这部分在Android上做就可以了。

cc -O3 -lbz2    bsdiff.c   -o bsdiff
cc -O3 -lbz2    bspatch.c   -o bspatch
bspatch.c:39:21: error: unknown type name 'u_char'; did you mean 'char'?
static off_t offtin(u_char *buf)
                    ^~~~~~
                    char
bspatch.c:65:8: error: expected ';' after expression
        u_char header[32],buf[8];
              ^
              ;
bspatch.c:65:2: error: use of undeclared identifier 'u_char'; did you mean
      'putchar'?
        u_char header[32],buf[8];
        ^~~~~~
        putchar
/usr/include/stdio.h:261:6: note: 'putchar' declared here
int      putchar(int);
         ^
bspatch.c:65:9: error: use of undeclared identifier 'header'
        u_char header[32],buf[8];
               ^
bspatch.c:65:20: error: use of undeclared identifier 'buf'
        u_char header[32],buf[8];
        
...
   

缺少头文件,编辑bspatch.c加入头文件可以解决

#include <sys/types.h>

该文件是存在于

/usr/include/sys/types.h

再次执行make命令,成功的生成了两个可执行文件bsdiff、bspatch,前者用于差分生成补丁包,后者用于合并补丁生成新的apk包。

2.2、增量文件的生成与合并

使用AndroidStudio简单生成两个apk,old.apk、new.apk
注意:每次生成apk最好clean一次工程

使用以下命令./bsdiff old.apk new.apk patch.patch

lexdeMacBook-Pro:bsdiff-4.3 lex$ ./bsdiff old.apk new.apk patch.patch

得到了patch.patch补丁
然后使用以下命令./bspatch old.apk new2.apk patch.patch

lexdeMacBook-Pro:bsdiff-4.3 lex$ ./bspatch old.apk new2.apk patch.patch

得到了新的apk,new2.apk

这个时候可以安装查看,也可以使用MD5来校验new.apk、new2.apk是否一致

lexdeMacBook-Pro:bsdiff-4.3 lex$ md5 new.apk
MD5 (new.apk) = 322b5a702bc1507e547c24f05109d812
lexdeMacBook-Pro:bsdiff-4.3 lex$ md5 new2.apk
MD5 (new2.apk) = 322b5a702bc1507e547c24f05109d812

事实上已经可以说明两个文件几乎一致了,说明这个操作是可行的了。

3、Android上实现增量更新

但这个怎么在Android上使用呢?这时候就要使用到NDK的知识了。

流程应该是这样的:
① 服务器端生成patch补丁
② 客户端通过对应的版本号获取相应的patch补丁
③ 客户端将本地apk跟patch补丁合并,生成新的apk
④ 安装新的apk从而实现了增量更新

这里作为demo的话,就只要实现将本地apk跟patch补丁合并生成新apk,然后安装看看是否成功就可以了。

3.1、新建一个C++ support工程

具体步骤不赘述,AndroidStudio默认使用CMake的形式
不要忘记添加SD卡读写权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
3.2、NDK相关代码的配置

copy相关文件到工程jni目录下
复制bspatch.c到jni目录下
复制bzip2文件夹到jni目录下,只留下头文件.h和源文件.c

image

修改CMakeLists.txt,主要是添加了需要编译的.c源码和.h头文件,这里要全部都编译,所以全部都添加到编译列表里面去

cmake_minimum_required(VERSION 3.4.1)

add_library( 
             SHARED
             
             src/main/jni/bspatch.c
             src/main/jni/bzip2/blocksort.c
             src/main/jni/bzip2/bzip2.c
             src/main/jni/bzip2/bzip2recover.c
             src/main/jni/bzip2/bzlib.c
             src/main/jni/bzip2/bzlib.h
             src/main/jni/bzip2/bzlib_private.h
             src/main/jni/bzip2/compress.c
             src/main/jni/bzip2/crctable.c
             src/main/jni/bzip2/decompress.c
             src/main/jni/bzip2/dlltest.c
             src/main/jni/bzip2/huffman.c
             src/main/jni/bzip2/mk251.c
             src/main/jni/bzip2/randtable.c
             src/main/jni/bzip2/spewG.c
             src/main/jni/bzip2/unzcrash.c )

find_library(
              log-lib

              log )

target_link_libraries(
                       bspatch

                       ${log-lib} )

修改bspatch.c里面的include,以及include jni

#include "bzip2/bzlib.h"
#include <jni.h>

编写增量更新工具类BsPatchUtil.java,写native方法

/**
 * 增量更新工具类
 * Created by lex.
 */

public class BsPatchUtil {
    static {
        System.loadLibrary("bspatch");
    }
    public static native int bspatch(String oldApk, String newApk, String patch);
}

选中bspatch方法,按alt + enter,生成源代码方法,编写jni代码,传入参数。patchMethod()就是bspatch.c中的main()方法

JNIEXPORT jint JNICALL
Java_com_example_lex_bsdiff_BsPatchUtil_bspatch(JNIEnv *env, jclass type, jstring oldApk_, jstring newApk_, jstring patch_) {
    const char *oldApk = (*env)->GetStringUTFChars(env, oldApk_, 0);
    const char *newApk = (*env)->GetStringUTFChars(env, newApk_, 0);
    const char *patch = (*env)->GetStringUTFChars(env, patch_, 0);

    // TODO
    int argc = 4;
    char *argv[argc];
    argv[0] = "bspatch";
    argv[1] = (char *) oldApk;
    argv[2] = (char *) newApk;
    argv[3] = (char *) patch;
    int ret = patchMethod(argc, argv);

    (*env)->ReleaseStringUTFChars(env, oldApk_, oldApk);
    (*env)->ReleaseStringUTFChars(env, newApk_, newApk);
    (*env)->ReleaseStringUTFChars(env, patch_, patch);

    return ret;
}

编译后会发现,报main方法重复的错误

Error:(70) multiple definition of `main'

找到各个对应文件,找到main方法,注释掉就可以编译成功了。

3.3、编写Android代码实现增量更新
/**
 * 点击按钮触发本地apk与补丁的合并
 */
public void patch(View btn) {
    String oldApk = getSourceDir();
    String newApk = getNewApkPath();
    String patch = getPatchPath();
    // 注意文件判空,否则崩溃
    BsPatchUtil.bspatch(oldApk, newApk, patch);
    // 如果成功了,调用intent去自动去安装apk
    // ...
}

/**
 * 获取本地apk的路径
 */
public String getSourceDir() {
    String sourceDir = getApplicationInfo().sourceDir;
    Log.i(TAG, "sourceDir = " + sourceDir);

    return sourceDir;
}

/**
 * 生成新apk的路径
 */
public String getNewApkPath() {
    String newApkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "newApk.apk";
    Log.i(TAG, "newApkPath = " + newApkPath);
    return newApkPath;
}

/**
 * 获取补丁patch的路径
 */
public String getPatchPath() {
    String patch = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + "patch.patch";
    Log.i(TAG, "patch = " + patch);
    return patch;
}

安装新的apk,成功!

源码已上传到 github
感谢鸿洋_大神的 Android 增量更新完全解析 是增量不是热修复

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 增量更新在Android开发中是一种很常见的技术。 增量更新的原理 增量更新的原理非常简单,就是将本地apk与服务...
    re冷星阅读 1,525评论 3 3
  • 注:首发地址 0. 前言 如果只学理论,不做实践,不踩踩坑,一般很难发现真正实践项目中的问题的,也比较难以加深对技...
    cfanr阅读 9,375评论 4 51
  • @[增量更新,差分包,bsdiff/patch] 背景 随着Android app的不断迭代升级,功能越来越多,a...
    SunYo阅读 13,644评论 2 7
  • 1.概述 1.1.什么是应用增量更新 当我们要更新一个应用的时候,以前很多更新的做法是下载一个新版本去覆盖一个旧版...
    揚灵阅读 3,037评论 8 19
  • 2017.11.13 周一 高畅 今天学到了一个实用技巧,如何快速提高自己的写作能力。 富兰克林的文笔很不错,...
    畅_759c阅读 272评论 0 1